package com.errol.generic.sequence;

import com.renjia.epay.service.log.LogService;
import org.springframework.stereotype.Service;

/**
 * SnowFlake算法生成id的结果是一个64bit大小的整数，它的结构如下：
 * 1bit，不用，因为二进制中最高位是符号位，1表示负数，0表示正数。生成的id一般都是用整数，所以最高位固定为0。
 * 41bit-毫秒级时间戳。41位可以表示（2的41次减1）个数字，转换为年份可以使用 69 年。
 * 10bit-工作机器id，用来记录工作机器id。10位可以表示 [0,1024) 个节点。
 * 12bit-序列号，用来记录同一毫秒内产生的不同id。12位可以表示 [0,4095) 个序列
 * <p>
 * 此实现进行了简单改造，使用 42bit存时间戳，11bit存序列号，这样可以使用 139 年，单台服务器毫秒级并发 2048
 * - 简单测试，8 线程并发获取大概 1300/ms，12 线程并发获取大概 1600/ms，16 线程并发竞争过于激烈只有 1100/ms
 *
 * @author zhang xiao bin
 * @version v1.0
 * @date 2020/10/26
 * @since v
 **/
@Service
public class SnowFlakeIdServiceImpl implements UnifiedIdService {

    /**
     * 开始时间截，业务一旦上线，这个值就不允许修改，分布式环境下可以维护在服务中心
     * @since 2020/10/26
     */
    private final long startTimestamp = 1603682820274L;
    /**
     * 机器id所占的位数
     */
    private final long workerIdBits = 10L;
    /**
     * 支持的最大机器id，结果是 1023
     */
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
    /**
     * 序列在id中占的位数
     */
    private final long sequenceBits = 11L;
    /**
     * 机器ID向左移12位
     */
    private final long workerIdShift = sequenceBits;
    /**
     * 时间戳向左移21位(10+11)
     */
    private final long timestampShift = sequenceBits + workerIdBits;
    /**
     * 生成序列的掩码，这里为4095 (0b111111111111=0xfff=4095)
     */
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);
    /**
     * 工作机器ID [0, 1024)
     */
    private long workerId;
    /**
     * 毫秒内序列 [0, 2048)
     */
    private long sequence = 0L;
    /**
     * 上次生成ID的时间截
     */
    private long lastTimestamp = -1L;
    private final LogService logService;

    public SnowFlakeIdServiceImpl(LogService logService) {
        this.logService = logService;
        // TODO: 20/10/26 多实例部署 workerId 通过数据库保证唯一，服务启动时由服务器ip或其他唯一标识，从数据库获取 workerId
        // 目前单机环境下就是默认 0
        workerId = 0;
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format("workerId can't be greater than %d or less than 0", maxWorkerId));
        }
    }

    @Override
    public synchronized long getLong() {
        long timestamp = timeGen();
        // 如果当前时间小于上一次ID生成的时间戳，说明系统时钟回退过，这个时候尝试等待到5毫秒或抛出异常
        if (timestamp < lastTimestamp) {
            timestamp = untilNextMillis(lastTimestamp);
        }
        // 如果是同一时间生成的，则进行毫秒内序列
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            // 毫秒内序列溢出
            if (sequence == 0) {
                // 阻塞到下一个毫秒,获得新的时间戳
                timestamp = untilNextMillis(lastTimestamp);
            }
        }
        // 时间戳改变，毫秒内序列重置
        else {
            sequence = 0L;
        }
        // 上次生成ID的时间截
        lastTimestamp = timestamp;
        // 移位并通过或运算拼到一起组成64位的ID
        return ((timestamp - startTimestamp) << timestampShift)
                | (workerId << workerIdShift)
                | sequence;
    }

    /**
     * 阻塞到下一个毫秒，直到获得新的时间戳
     *
     * @param lastTimestamp 上次生成ID的时间截
     * @return 当前时间戳
     */
    private long untilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            if (lastTimestamp - timestamp > 5) {
                throw new RuntimeException(logService.remind(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)));
            }
            timestamp = timeGen();
        }
        return timestamp;
    }

    /**
     * 返回以毫秒为单位的当前时间
     *
     * @return 当前时间(毫秒)
     */
    private long timeGen() {
        return System.currentTimeMillis();
    }
}
